fix(render): allow explicit entry without index#2307
Conversation
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
Reviewed at 2ee0ba79.
Clean follow-up to #2261. The three changes compose well:
resolveProjectOrThrow'srequireIndexoption — additive, defaults to backwards-compat (require). All other callers (beats.ts:72,benchmark.ts:61,check.ts:121,cloud/render.ts:512,540,compositions.ts:177,info.ts:56, etc.) callresolveProject(args.dir)positionally, so they keep the strict gate. Onlyrender.tsopts into the relaxed mode. Good scoping.renderTargethoisted to a single site atrender.ts:403— replaces two inline duplications in the slideshow probe (line 719) and resolution preflight (line 854). Same expressionentryFile ? resolve(project.dir, entryFile) : project.indexPathnow consumed by batch (indexPath: renderTargetat 701), slideshow, lint (entryFile ? renderTarget : undefinedat 818), resolution preflight, and variables (validateVariablesAgainstProject(renderTarget, variables)at 945). One primitive, five consumers — no drift possible.explicitEntry = entryFile ? renderTarget : undefined— now an absolute path, matching the "absolute" contract Miguel's comment at 815 documents.lintProjectin #2261 resolves both cases (resolve(entryFile)is idempotent on absolutes), so the contract change is consistent.
Nits
-
🟡 The two composition-related guards use slightly different heuristics:
hasExplicitCompositionat line 393 checks 4 sentinels:!= null,!== "",!== ".",!== "./". FeedsresolveProject({ requireIndex: !hasExplicit }).explicitEntryat 818 usesentryFile ? renderTarget : undefined— relies onresolveCompositionEntryArgreturningundefinedwhen composition is empty/default.
If
resolveCompositionEntryArgandhasExplicitComposition's inline logic ever diverge (e.g. a whitespace-only--composition " "—hasExplicitwould trim to""and treat as false;resolveCompositionEntryArgdoesn't trim in the same way), you'd getrequireIndex: truebut a non-null entryFile, or the reverse. Neither is fatal (fails downstream onreadFileSync), just an internal-consistency wart. One sharedhasExplicitCompositionderived once, used everywhere, would collapse this. Doesn't need to happen in this PR. -
🟡 Test coverage: the new
resolveProjectOrThrowtest provesrequireIndex: falseaccepts a bare directory. But there's no end-to-end test inrender.ts(or inproject.test.ts) that covers "no index.html + explicit composition → render succeeds." Reasonable to leave for integration coverage; ratchets down testing surface area.
LGTM. Stack looks good end-to-end.
Addressed in |
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
R2 reviewed at 9577441c, delta from R1 head 2ee0ba79 (base #2261 also refreshed in this stack).
R1 nit on hasExplicitComposition vs parseCompositionEntryArg divergence cleanly addressed:
- One classifier, no whitespace divergence — extracted
normalizeCompositionEntryArg+hasExplicitCompositionArgatpackages/cli/src/utils/renderArgs.ts:131and reused fromrender.ts:392. Both call sites now go through the same.trim().replace(/^\.\//, "")+"." → undefinednormalization, so pathological inputs (" ","./",".") can't split behavior between "gate the strict lint" and "resolve the entry file" anymore. - Test locks the invariant —
hasExplicitCompositionArgtest explicitly enumerates[undefined, "", " ", ".", "./"]all →falseand"./compositions/intro.html"→true. Plus the new render-command integration test atrender.test.ts:1071renders an explicit composition from a project with noindex.html, so the strict-mode bypass is end-to-end verified.
LGTM from my side.
jrusso1020
left a comment
There was a problem hiding this comment.
Approving on Rames-D's go (R2 delta clean, nit round addressed) + Magi's CI-green confirmation. Verified at final head 9577441c: CI green, no changes-requested, mergeable.
9577441 to
716adc6
Compare
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
R3 reviewed at 716adc60, post-rebase onto merged main. Delta from R2 head 9577441c.
Most of the child diff is unchanged from R2, but the rebase does introduce one substantive delta at the pre-render lint call site that's worth calling out because it silently fixes a hidden interaction with the just-merged #2261 guard:
Pre-render lint now threads the absolute renderTarget instead of the project-relative entryFile:
// lintProject's explicit-entry contract is an absolute source path;
// entryFile is project-relative for the producer.
const explicitEntry = entryFile ? renderTarget : undefined;Traced through: lintProject(projectDir, entryFile) does indexPath = resolve(entryFile) — for a relative entry that resolves against CWD, not projectDir. Combined with #2261's newly-landed isWithinProjectRoot(projectDir, indexPath) guard, the pre-render lint would have thrown Explicit lint entry is outside the project directory on every explicit-composition render invoked from any working directory other than the project root. R3's absolute-path threading avoids that regression entirely.
Bonus: the same rebase collapses the two inline renderTarget recomputations under slideshow-probe and resolution-preflight so the hoisted renderTarget at render.ts:397 is now the single source of truth (which also fixed variables validation at line 941 — validateVariablesAgainstProject(project.indexPath, ...) → validateVariablesAgainstProject(renderTarget, ...); the old code was silently validating variables against index.html even when rendering an explicit entry).
Everything else — hasExplicitCompositionArg classifier, normalizeCompositionEntryArg shared normalization, resolveProjectOrThrow(dirArg, { requireIndex }), both new tests — is byte-identical to R2.
LGTM from my side.
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
R4 reviewed at e3e847c1, delta from R3 head 716adc60.
One-line change: raises the per-test timeout on the new renders an explicit composition from a project with no index.html test to 60s. Scoped to that one integration test — every other assertion in the file keeps Vitest's default, so this doesn't broadly loosen the timeout budget. Matches the failure signature Magi described (test-only timeout under full parallel monorepo load, no assertion failure).
LGTM from my side.
jrusso1020
left a comment
There was a problem hiding this comment.
Re-approving on Rames-D's R4 go. Final head e3e847c1 fully green (earlier reds were GHA infra + an unrelated Windows-load test timeout, all rerun clean per Magi); the only delta from R3 is the one-line 60s test-timeout bump. No changes-requested, mergeable.
Summary
render --composition <file>resolve a valid project directory even when rootindex.htmlis absentindex.htmlrequirement for every default renderRelationship
Stacked on #2261 because that PR introduces explicit-entry lint scoping. This follow-up fixes the earlier project-discovery gate and its relative-vs-absolute lint call.
Reproduction
npx hyperframes@0.7.55 render -c "Branching Narratives 02 - The Knock.dc.html" ...in a directory containing that file but noindex.htmlexits before resolving-c, despite help saying the flag renders a specific file instead ofindex.html.Verification
bun run --cwd packages/cli test --run src/utils/project.test.ts src/utils/renderArgs.test.ts src/utils/lintProject.test.ts(102 passed)bun run --cwd packages/cli typecheck.dc.htmlwith no index to H.264; ffprobe: 320x180, 24fps, 1.000s